﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Caching;
using System.Web.Hosting;
using System.Web.UI;
using OpenWaves.Reflection;
using OpenWaves.Web;

#if NET_40_OR_GREATER
[assembly: PreApplicationStartMethod(typeof(AssemblyResourceVirtualPathProvider), "EnsureRegistered")]
#endif

namespace OpenWaves.Web
{
    public class AssemblyResourceVirtualPathProvider : VirtualPathProvider
    {
        private const string AppResourceFragmentName = "App_Resource";
        private static AssemblyResourceVirtualPathProvider RegisteredInstance;
        private static readonly object RegistrationSyncRoot = new object();

        /// <summary>
        /// For test usage only
        /// </summary>
        public AssemblyResourceVirtualPathProvider()
        {
        }

        public static IEnumerable<string> GetPublicResourceNames(Assembly assembly)
        {
            Contract.Requires<ArgumentNullException>(assembly != null);
            Contract.Ensures(Contract.Result<IEnumerable<string>>() != null);

            return assembly.GetCustomAttributes<PublicAssemblyResourcesAttribute>().SelectMany(a => a.ResourceNames);
        }

        public static void EnsureRegistered()
        {
            if (RegisteredInstance != null)
                return;

            lock (RegistrationSyncRoot)
            {
                if (RegisteredInstance != null)
                    return;

                RegisteredInstance = new AssemblyResourceVirtualPathProvider();
                try
                {
                    HostingEnvironment.RegisterVirtualPathProvider(RegisteredInstance);
                }
                catch
                {
                    RegisteredInstance = null;
                    throw;
                }
            }
        }

        public static Url CreateUrl(Type type)
        {
            Contract.Requires<ArgumentNullException>(type != null);
            Contract.Ensures(Contract.Result<Url>() != null);

            string extension = GetExtension(type);
            return CreateUrl(type, extension);
        }

        public static Url CreateUrl(Type type, string extension)
        {
            Contract.Requires<ArgumentNullException>(type != null);
            Contract.Requires<ArgumentNullException>(String.IsNullOrEmpty(extension) == false);
            Contract.Requires<ArgumentNullException>(extension.StartsWith(".", StringComparison.Ordinal));
            Contract.Ensures(Contract.Result<Url>() != null);

            var assembly = type.Assembly;
            var resourceName = type.FullName + extension;

            return CreateUrl(assembly, resourceName);
        }

        public static Url CreateUrl(Assembly assembly, string resourceName)
        {
            Contract.Requires(assembly != null);
            Contract.Requires<ArgumentNullException>(String.IsNullOrEmpty(resourceName) == false);
            Contract.Ensures(Contract.Result<Url>() != null);

            var urlPath = UrlPath.AppRelative(AppResourceFragmentName, assembly.GetName().Name, resourceName);
            return urlPath.ToUrl();
        }

        private static string GetExtension(Type type)
        {
            Contract.Requires<ArgumentNullException>(type != null);
            Contract.Requires<ArgumentException>(type.Is<Page>() || type.Is<UserControl>() || type.Is<IHttpHandler>());
            Contract.Ensures(String.IsNullOrEmpty(Contract.Result<string>()) == false);
            Contract.Ensures(Contract.Result<string>().StartsWith(".", StringComparison.Ordinal));

            if (type.Is<Page>())
                return ".aspx";

            if (type.Is<MasterPage>())
                return ".master";

            if (type.Is<UserControl>())
                return ".ascx";

            if (type.Is<IHttpHandler>())
                return ".ashx";

            throw new ArgumentException(
                "Cannot determine extension for type '{0}'. It is not assignable from any of the supported types.");
        }

        protected virtual bool IsAppResourcePath(string virtualPath)
        {
            const string AppResourcePathPrefix = "~/" + AppResourceFragmentName + "/";

            if (VirtualPathUtility.IsAppRelative(virtualPath) == false && Path.IsPathRooted(virtualPath))
                virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);

            return virtualPath.StartsWith(AppResourcePathPrefix, StringComparison.OrdinalIgnoreCase);
        }

        public override bool FileExists(string virtualPath)
        {
            return (IsAppResourcePath(virtualPath) || base.FileExists(virtualPath));
        }

        public override VirtualFile GetFile(string virtualPath)
        {
            if (IsAppResourcePath(virtualPath))
                return new AssemblyResourceVirtualFile(virtualPath);

            return base.GetFile(virtualPath);
        }

        public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
        {
            var pathDependencies = virtualPathDependencies.Cast<string>().ToList();

            var resourceBasedDependencies = pathDependencies.Where(this.IsAppResourcePath).ToList();
            var nonResourceBasedDependencies = pathDependencies.Where(p => IsAppResourcePath(p) == false).ToList();

            return String.Concat(
                base.GetFileHash(virtualPath, nonResourceBasedDependencies) ,
                resourceBasedDependencies.Select(p=>new AssemblyResourceVirtualFile(p)).Select(f=>this.GetAssemblyHash(f.Assembly)).JoinStrings());
        }

        private string GetAssemblyHash(Assembly assembly)
        {
            if (String.IsNullOrEmpty(assembly.Location))
                return assembly.CodeBase;

            return File.GetLastWriteTimeUtc(assembly.Location).ToString(CultureInfo.InvariantCulture);
        }

        public override CacheDependency GetCacheDependency(string virtualPath,
                                                           IEnumerable virtualPathDependencies,
                                                           DateTime utcStart)
        {
            var nonResourceBasedDependencies = virtualPathDependencies.Cast<string>().Where(p => IsAppResourcePath(p) == false);

            return base.GetCacheDependency(virtualPath, nonResourceBasedDependencies, utcStart);
        }
    }
}